package data

import (
	"encoding/binary"
	"hash/crc32"
)

// LogRecordType 日志记录类型，byte别名
type LogRecordType = byte

const (
	LogRecordNormal      LogRecordType = iota // 数据类型
	LogRecordDeleted                          // 数据删除类型
	LogRecordTxnFinished                      // 事务提交类型
)

// CRC + Type + Key Size + Value Size
// 4  +   1  +    5     +    5
// maxLogRecordHeaderSize 一条数据记录的头部最大长度，key和value是变长
const maxLogRecordHeaderSize = binary.MaxVarintLen32*2 + 5

// LogRecord 数据记录信息
type LogRecord struct {
	Key   []byte
	Value []byte
	Type  LogRecordType
}

// logRecordHeader LogRecord 的头部信息
type logRecordHeader struct {
	crc        uint32        // crc 校验值
	recordType LogRecordType // 标识 LogRecord 类型
	keySize    uint32        // key 长度
	valueSize  uint32        // value 长度
}

// LogRecordPos 数据内存索引，描述数据在磁盘上的位置
type LogRecordPos struct {
	Fid    uint32 // 文件 id，表示将数据存储到了哪个文件中
	Offset int64  // 偏移
	Size   uint32 // 数据所占大小
}

// TransactionRecord 暂存的事务数据
type TransactionRecord struct {
	Record *LogRecord
	Pos    *LogRecordPos
}

// EncodeLogRecord 将 LogRecord 编码，返回编码后的字节数组和对应长度
// crc(4) + type(1) + keySize(max(5)) + valueSize(max(5)) + key + value
func EncodeLogRecord(logRecord *LogRecord) ([]byte, int64) {
	// 创建 header 头部信息
	header := make([]byte, maxLogRecordHeaderSize)
	// 第五个字节存储类型
	header[4] = logRecord.Type

	// 存储变长数据
	idx := 5
	idx += binary.PutVarint(header[idx:], int64(len(logRecord.Key)))
	idx += binary.PutVarint(header[idx:], int64(len(logRecord.Value)))
	// 数据日志长度
	size := idx + len(logRecord.Key) + len(logRecord.Value)
	// 最终数据日志字节数组
	encBytes := make([]byte, size)
	// 拷贝头部信息
	copy(encBytes[:idx], header[:idx])
	// 拷贝kv数据
	copy(encBytes[idx:], logRecord.Key)
	copy(encBytes[idx+len(logRecord.Key):], logRecord.Value)

	// crc32 循环校验和写入
	crc := crc32.ChecksumIEEE(encBytes[4:])
	binary.LittleEndian.PutUint32(encBytes[:4], crc)

	return encBytes, int64(size)
}

// decodeLogRecordHeader LogRecord 的字节数据解码，返回头部信息及头部信息长度
func decodeLogRecordHeader(buf []byte) (*logRecordHeader, int64) {
	// 如果crc所占字节都不够，数据有误
	if len(buf) <= 4 {
		return nil, 0
	}
	// 头部信息构建
	header := &logRecordHeader{
		crc:        binary.LittleEndian.Uint32(buf[:4]),
		recordType: buf[4],
	}

	idx := 5
	// 处理变长整数格式数据
	// 每个字节最高位1表示还有，0表示后续没有数据了
	// 返回数据值和所占字节数
	keySize, n := binary.Varint(buf[idx:])
	idx += n
	valueSize, m := binary.Varint(buf[idx:])
	idx += m

	header.keySize = uint32(keySize)
	header.valueSize = uint32(valueSize)
	return header, int64(idx)
}

// EncodeLogRecordPos 对索引信息进行编码
func EncodeLogRecordPos(pos *LogRecordPos) []byte {
	buf := make([]byte, binary.MaxVarintLen32*2+binary.MaxVarintLen64)
	var idx = 0
	idx += binary.PutVarint(buf[idx:], int64(pos.Fid))
	idx += binary.PutVarint(buf[idx:], pos.Offset)
	idx += binary.PutVarint(buf[idx:], int64(pos.Size))
	return buf[:idx]
}

// DecodeLogRecordPos 解码 LogRecordPos
func DecodeLogRecordPos(buf []byte) *LogRecordPos {
	var idx = 0
	fileId, n := binary.Varint(buf[idx:])
	idx += n
	offset, m := binary.Varint(buf[idx:])
	idx += m
	size, _ := binary.Varint(buf[idx:])
	return &LogRecordPos{
		Fid:    uint32(fileId),
		Offset: int64(offset),
		Size:   uint32(size),
	}
}

// getLogRecordCRC 根据 LogRecord 中的 kv 信息，结合 header 头部信息，计算 CRC
func getLogRecordCRC(logRecord *LogRecord, header []byte) uint32 {
	if logRecord == nil {
		return 0
	}

	crc := crc32.ChecksumIEEE(header[:])
	crc = crc32.Update(crc, crc32.IEEETable, logRecord.Key)
	crc = crc32.Update(crc, crc32.IEEETable, logRecord.Value)

	return crc
}
